﻿namespace Ast
open System
open Common

type EvalType =
    /// Return a complete tree evaluation
    | Complete
    /// Generalized Tree evaluation
    | Generalized of InfoGen
    /// UNUSED
    /// Return the tree evaluation without factors, replaced with string "_v1","_v2",...
    | WithoutFactor
    /// UNUSED
    ///ATTENTION: Assuming a tree with x,y and z as factors, return the string "x,y,z,"
    | OnlyFactor

type Factor =
    | Number  of String
    | Range   of String
    | Bool    of bool
    | Str     of String

    member f.Eval (opt:EvalType) = 
        let varName = "_v"+variables.Count.ToString()
        match f with
        | Number x ->
            match opt with
            | EvalType.WithoutFactor ->
                Common.variables.Add(varName+":float")
                varName
            | _ -> if x.Contains(".") then x else x+"."
        | Range x ->
            match opt with
            | EvalType.WithoutFactor ->
                if x.StartsWith("Range") then  // if isn't a single cell
                    Common.variables.Add(varName+":Range")
                else Common.variables.Add(varName)
                varName
            | EvalType.Generalized infogen ->                
                let parts = x.Split(':')
                if parts.Length = 1 then
                    let index,height = Common.cellWithinRanges x infogen.Dip
                    "t_" + Common.rng2var (infogen.Dip.[index]) + ".Current.["+height.ToString()+"]"
                else
                    let mutable outp = Text.StringBuilder("RangeFromCol( [|")
                    for vert in Common.columnsFromRng x do
                        let parts = vert.Split(':')
                        let i,hi = Common.cellWithinRanges parts.[0] infogen.Dip
                        let last = parts.[0].Split('!').[0] + "!" + parts.[1]
                        let j,hj = Common.cellWithinRanges last infogen.Dip
                        outp.Append("t_" + Common.rng2var (infogen.Dip.[i]) + ".Current.["+hi.ToString()+".."+hj.ToString()+"];" ) |>ignore
                    outp.[outp.Length-1] <- '|'
                    outp.Append("] )") |>ignore
                    outp.ToString()
            | _ -> 
                if Common.myInputs.ContainsKey(x) then
                    Common.myInputs.[x]
                elif Common.myGeneralized.ContainsKey(x) then
                    Common.rng2var x
                else
                   // "Range(\""+x+"\")"
                    if (x.Split('!').[1]).Contains(":") then "Range(\""+x+"\")"
                    else "Rif("+ x.Replace("!","_") + ")"
        | Bool x -> 
            match opt with
            | EvalType.WithoutFactor ->
                Common.variables.Add(varName+":bool")
                varName
            | _ -> if x then "true" else "false"
        | Str x -> 
            match opt with
            | EvalType.WithoutFactor ->
                Common.variables.Add(varName+":string")
                varName
            | _ -> x

    member this.checkDrag (f1:Factor) (ind:int) =
        match this with
        | Number _  -> match f1 with
                        | Number _ -> true
                        | _ -> false
        | Range r1   -> match f1 with
                        | Range r0 -> let rr = r0.Split(':')
                                      let my = r1.Split(':')
                                      if rr.Length<>my.Length then false  // 1 or 2
                                      else
                                          let mutable ok = true
                                          for i in 0..rr.Length-1 do
                                              ok <- ok &&
                                                    let rowNumb = snd (Common.A1toPair rr.[i])
                                                    (my.[i].EndsWith(string (rowNumb+ind)))
                                          ok
                        | _ -> false
        | Bool _ -> match f1 with
                        | Bool _ -> true
                        | _ -> false
        | Str _  -> match f1 with
                        | Str _ -> true
                        | _ -> false
    
and Expr =
    | Factor    of Factor
    | Func      of String * Expr list
    | Bop       of String * Expr * Expr
    | Brakets   of Expr
    | Plmn      of String * Expr
    | Cond      of String * Expr * Expr
    | Empty

    member this.Eval (opt:EvalType) =  
       match this with
        | Empty       -> "null"
        | Factor fact -> 
            if opt=EvalType.OnlyFactor then
                fact.Eval opt + ","
            else fact.Eval opt
        | Func(f, lparam)  ->   // with lparam.Length>=1
            if opt=EvalType.OnlyFactor then
                let mutable p = new System.Text.StringBuilder()
                for el in lparam do 
                    p <- p.Append(el.Eval opt)
                p.ToString()
            else
                let mutable p = new System.Text.StringBuilder("[")
                for el in lparam do 
                    p <- p.Append(el.Eval opt).Append(";")
                p.[p.Length-1] <- ']'
                f+"("+p.ToString()+")"
        | Bop (op, e1, e2) ->
            let resolve (exp:Expr) = 
                    match exp with
                     | Factor f -> match f with
                                    | Factor.Range r -> match opt with
                                                         | Generalized _ -> 
                                                              "("+(f.Eval opt)+":?>float)"
                                                         | _ -> if op="<+>" then (f.Eval opt)
                                                                else "(float ("+(f.Eval opt)+"))"
                                    | Str s -> if op="<+>" then (f.Eval opt)
                                               else "(float "+(f.Eval opt)+")"
                                    | Bool b -> if b then "1." else "0."
                                    | _ -> f.Eval opt
                     
                     | Bop (_,_,_) -> exp.Eval opt
                     | _ -> if op="<+>" then exp.Eval opt
                            else "(float ("+exp.Eval opt+"))"
                                       
                
            if opt=EvalType.OnlyFactor then
                (resolve e1) + (resolve e2)
            else
                (resolve e1) + op + (resolve e2)

        | Cond (op, e1, e2) ->
            if opt=EvalType.OnlyFactor then
                (e1.Eval opt) + (e2.Eval opt)
            else
                (e1.Eval opt) + op + (e2.Eval opt)
        | Brakets expr      -> 
            if opt=EvalType.OnlyFactor then
                (expr.Eval opt)
            else "(" + (expr.Eval opt) + ")"
        | Plmn (sign,expr)  -> 
            sign + (expr.Eval opt)

    member this.check (tree:Expr) (ind:int) =
        match this with
        | Bop (op0,ex1,ex2)  -> match tree with
                                | Bop (op1,son1,son2) -> 
                                    op0.Equals(op1) && ex1.check son1 ind && ex2.check son2 ind
                                | _ -> false
        | Cond (op0,ex1,ex2) -> match tree with
                                | Cond (op1,son1,son2) ->
                                    op0.Equals(op1) && ex1.check son1 ind && ex2.check son2 ind
                                | _ -> false
        | Plmn (op,ex)       -> match tree with
                                | Plmn(op1,ex1) -> op.Equals(op1) && (ex.check ex1 ind)
                                | _ -> false
        | Func (fn0,par0)    -> match tree with
                                | Func (fn1,par1) ->
                                    fn0.Equals(fn1) && par0.Length=par1.Length &&
                                    List.forall2 (fun (a:Expr) b -> a.check b ind) par0 par1 
                                | _ -> false
        | Factor f0   -> match tree with
                         | Factor f1 -> f0.checkDrag f1 ind
                         | _ -> false
        | Brakets ex0 -> match tree with
                         | Brakets ex1 -> ex0.check ex1 ind
                         | _ -> false
        | Empty       -> false

    member this.dragged (tree:Expr) (ind:int) = 
        if this.check tree ind then true
        else
            match this with
            | Bop (_,ex1,ex2)
            | Cond (_, ex1,ex2) ->  (ex1.dragged tree ind) || (ex2.dragged tree ind)
            | Plmn (_,ex)
            | Brakets ex        ->  ex.dragged tree ind
            | Func (_,param)    ->  let mutable i=0
                                    while i<param.Length && (not (param.[i].dragged tree ind)) do
                                        i<-i+1
                                    if i=param.Length then false
                                    else true
            | Factor _ 
            | Empty     -> false
        
and Formula =
    | Exp of Expr
    | ConstCell of String

    member this.Eval (options:EvalType) =
      //  Common.resetInfo()
        match this with
        | Exp e0 -> e0.Eval options
        | ConstCell c -> c

    member this.dragged (tree:Formula) (ind:int) =
        match this with
        | Exp e0 -> match tree with
                    | Exp e1 -> e0.dragged e1 ind
                    | ConstCell _ -> false
        | ConstCell _ -> false

